using System.Diagnostics;
using Community.CsharpSqlite.Entity;

namespace Community.CsharpSqlite
{
    public  class Delete
    {
        /*
    ** 2001 September 15
    **
    ** The author disclaims copyright to this source code.  In place of
    ** a legal notice, here is a blessing:
    **
    **    May you do good and not evil.
    **    May you find forgiveness for yourself and forgive others.
    **    May you share freely, never taking more than you give.
    **
    *************************************************************************
    ** This file contains C code routines that are called by the parser
    ** in order to generate code for DELETE FROM statements.
    *************************************************************************
    **  Included in SQLite3 port to C#-SQLite;  2008 Noah B Hart
    **  C#-SQLite is an independent reimplementation of the SQLite software library
    **
    **  SQLITE_SOURCE_ID: 2010-01-05 15:30:36 28d0d7710761114a44a1a3a425a6883c661f06e7
    **
    **  $Header: Community.CsharpSqlite/src/delete_c.cs,v bcbd36f24b23 2010/02/18 17:35:24 Noah $
    *************************************************************************
    */
        //#include "sqliteInt.h"

        /*
    ** Look up every table that is named in pSrc.  If any table is not found,
    ** add an error message to pParse.zErrMsg and return NULL.  If all tables
    ** are found, return a pointer to the last table.
    */

        private static Table sqlite3SrcListLookup(Parse pParse, SrcList pSrc)
        {
            SrcList_item pItem = pSrc.a[0];
            Table pTab;
            Debug.Assert(pItem != null && pSrc.nSrc == 1);
            pTab = Build.LocateTable(pParse, 0, pItem.zName, pItem.zDatabase);
            Build.DeleteTable(ref pItem.pTab);
            pItem.pTab = pTab;
            if (pTab != null)
            {
                pTab.nRef++;
            }
            if (sqlite3IndexedByLookup(pParse, pItem) != 0)
            {
                pTab = null;
            }
            return pTab;
        }

        /*
    ** Check to make sure the given table is writable.  If it is not
    ** writable, generate an error message and return 1.  If it is
    ** writable return 0;
    */

        private static bool sqlite3IsReadOnly(Parse pParse, Table pTab, int viewOk)
        {
            /* A table is not writable under the following circumstances:
      **
      **   1) It is a virtual table and no implementation of the xUpdate method
      **      has been provided, or
      **   2) It is a system table (i.e. sqlite_master), this call is not
      **      part of a nested parse and writable_schema pragma has not
      **      been specified.
      **
      ** In either case leave an error message in pParse and return non-zero.
      */
            if (
                (Utility.IsVirtual(pTab)
                 && sqlite3GetVTable(pParse.db, pTab).pMod.pModule.xUpdate == null)
                || ((pTab.tabFlags & TabFlag.TF_Readonly) != 0
                    && (pParse.db.flags & Flag.SQLITE_WriteSchema) == 0
                    && pParse.nested == 0)
                )
            {
                Utility.Sqlite3ErrorMsg(pParse, "table %s may not be modified", pTab.zName);
                return true;
            }

#if !SQLITE_OMIT_VIEW
            if (viewOk == 0 && pTab.pSelect != null)
            {
                Utility.Sqlite3ErrorMsg(pParse, "cannot modify %s because it is a view", pTab.zName);
                return true;
            }
#endif
            return false;
        }


#if !SQLITE_OMIT_VIEW && !SQLITE_OMIT_TRIGGER
        /*
** Evaluate a view and store its result in an ephemeral table.  The
** pWhere argument is an optional WHERE clause that restricts the
** set of rows in the view that are to be added to the ephemeral table.
*/

        private static void sqlite3MaterializeView(
            Parse pParse, /* Parsing context */
            Table pView, /* View definition */
            Expr pWhere, /* Optional WHERE clause to be added */
            int iCur /* VdbeCursor number for ephemerial table */
            )
        {
            var dest = new SelectDest();
            Select pDup;
            sqlite3 db = pParse.db;

            pDup = ExprHelper.SelectDup(db, pView.pSelect, 0);
            if (pWhere != null)
            {
                SrcList pFrom;

                pWhere = ExprHelper.ExprDup(db, pWhere, 0);
                pFrom = Build.SrcListAppend(db, null, null, null);
                //if ( pFrom != null )
                //{
                Debug.Assert(pFrom.nSrc == 1);
                pFrom.a[0].zAlias = pView.zName; // sqlite3DbStrDup( db, pView.zName );
                pFrom.a[0].pSelect = pDup;
                Debug.Assert(pFrom.a[0].pOn == null);
                Debug.Assert(pFrom.a[0].pUsing == null);
                //}
                //else
                //{
                //  sqlite3SelectDelete( db, ref pDup );
                //}
                pDup = sqlite3SelectNew(pParse, null, pFrom, pWhere, null, null, null, 0, null, null);
            }
            sqlite3SelectDestInit(dest, SelectResultType.SRT_EphemTab, iCur);
            sqlite3Select(pParse, pDup, ref dest);
            sqlite3SelectDelete(db, ref pDup);
        }
#endif
        //* !SQLITE_OMIT_VIEW) && !SQLITE_OMIT_TRIGGER) */

#if (SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !(SQLITE_OMIT_SUBQUERY)
/*
** Generate an expression tree to implement the WHERE, ORDER BY,
** and LIMIT/OFFSET portion of DELETE and UPDATE statements.
**
**     DELETE FROM table_wxyz WHERE a<5 ORDER BY a LIMIT 1;
**                            \__________________________/
**                               pLimitWhere (pInClause)
*/
Expr sqlite3LimitWhere(
Parse pParse,               /* The parser context */
SrcList pSrc,               /* the FROM clause -- which tables to scan */
Expr pWhere,                /* The WHERE clause.  May be null */
ExprList pOrderBy,          /* The ORDER BY clause.  May be null */
Expr pLimit,                /* The LIMIT clause.  May be null */
Expr pOffset,               /* The OFFSET clause.  May be null */
char zStmtType              /* Either DELETE or UPDATE.  For error messages. */
){
Expr pWhereRowid = null;    /* WHERE rowid .. */
Expr pInClause = null;      /* WHERE rowid IN ( select ) */
Expr pSelectRowid = null;   /* SELECT rowid ... */
ExprList pEList = null;     /* Expression list contaning only pSelectRowid */
SrcList pSelectSrc = null;  /* SELECT rowid FROM x ... (dup of pSrc) */
Select pSelect = null;      /* Complete SELECT tree */

/* Check that there isn't an ORDER BY without a LIMIT clause.
*/
if( pOrderBy!=null && (pLimit == null) ) {
Utility.Sqlite3ErrorMsg(pParse, "ORDER BY without LIMIT on %s", zStmtType);
pParse.parseError = 1;
goto limit_where_cleanup_2;
}

/* We only need to generate a select expression if there
** is a limit/offset term to enforce.
*/
if ( pLimit == null )
{
/* if pLimit is null, pOffset will always be null as well. */
Debug.Assert( pOffset == null );
return pWhere;
}

/* Generate a select expression tree to enforce the limit/offset
** term for the DELETE or UPDATE statement.  For example:
**   DELETE FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1
** becomes:
**   DELETE FROM table_a WHERE rowid IN (
**     SELECT rowid FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1
**   );
*/

pSelectRowid = ExprHelper.PExpr( pParse, TokenKeyword.TK_ROW, null, null, null );
if( pSelectRowid == null ) goto limit_where_cleanup_2;
pEList = ExprHelper.ExprListAppend( pParse, null, pSelectRowid);
if( pEList == null ) goto limit_where_cleanup_2;

/* duplicate the FROM clause as it is needed by both the DELETE/UPDATE tree
** and the SELECT subtree. */
pSelectSrc = ExprHelper.SrcListDup(pParse.db, pSrc,0);
if( pSelectSrc == null ) {
ExprHelper.ExprListDelete(pParse.db, pEList);
goto limit_where_cleanup_2;
}

/* generate the SELECT expression tree. */
pSelect = sqlite3SelectNew( pParse, pEList, pSelectSrc, pWhere, null, null,
pOrderBy, 0, pLimit, pOffset );
if( pSelect == null ) return null;

/* now generate the new WHERE rowid IN clause for the DELETE/UDPATE */
pWhereRowid = ExprHelper.PExpr( pParse, TokenKeyword.TK_ROW, null, null, null );
if( pWhereRowid == null ) goto limit_where_cleanup_1;
pInClause = ExprHelper.PExpr( pParse, TokenKeyword.TK_IN, pWhereRowid, null, null );
if( pInClause == null ) goto limit_where_cleanup_1;

pInClause->x.pSelect = pSelect;
pInClause->flags |= ExprFlag.EP_xIsSelect;
ExprHelper.ExprSetHeight(pParse, pInClause);
return pInClause;

/* something went wrong. clean up anything allocated. */
limit_where_cleanup_1:
sqlite3SelectDelete(pParse.db, pSelect);
return null;

limit_where_cleanup_2:
ExprHeler.ExprDelete(pParse.db, ref pWhere);
ExprHelper.ExprListDelete(pParse.db, pOrderBy);
ExprHeler.ExprDelete(pParse.db, ref pLimit);
ExprHeler.ExprDelete(pParse.db, ref pOffset);
return null;
}
#endif
        //* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY) */

        /*
** Generate code for a DELETE FROM statement.
**
**     DELETE FROM table_wxyz WHERE a<5 AND b NOT NULL;
**                 \________/       \________________/
**                  pTabList              pWhere
*/

        private static void sqlite3DeleteFrom(
            Parse pParse, /* The parser context */
            SrcList pTabList, /* The table from which we should delete things */
            Expr pWhere /* The WHERE clause.  May be null */
            )
        {
            Vdbe v; /* The virtual database engine */
            Table pTab; /* The table from which records will be deleted */
            string zDb; /* Name of database holding pTab */
            int end, addr = 0; /* A couple addresses of generated code */
            int i; /* Loop counter */
            WhereInfo pWInfo; /* Information about the WHERE clause */
            Index pIdx; /* For looping over indices of the table */
            int iCur; /* VDBE VdbeCursor number for pTab */
            sqlite3 db; /* Main database structure */
            AuthContext sContext; /* Authorization context */
            NameContext sNC; /* Name context to resolve expressions in */
            int iDb; /* Database number */
            int memCnt = -1; /* Memory cell used for change counting */
            int rcauth; /* Value returned by authorization callback */

#if !SQLITE_OMIT_TRIGGER
            bool isView; /* True if attempting to delete from a view */
            Trigger pTrigger; /* List of table triggers, if required */
#endif
            sContext = new AuthContext(); //memset(&sContext, 0, sizeof(sContext));

            db = pParse.db;
            if (pParse.nErr != 0 /*|| db.mallocFailed != 0 */)
            {
                goto delete_from_cleanup;
            }
            Debug.Assert(pTabList.nSrc == 1);

            /* Locate the table which we want to delete.  This table has to be
      ** put in an SrcList structure because some of the subroutines we
      ** will be calling are designed to work with multiple tables and expect
      ** an SrcList* parameter instead of just a Table* parameter.
      */
            pTab = sqlite3SrcListLookup(pParse, pTabList);
            if (pTab == null) goto delete_from_cleanup;

            /* Figure out if we have any triggers and if the table being
      ** deleted from is a view
      */
#if !SQLITE_OMIT_TRIGGER
            int iDummy = 0;
            pTrigger = sqlite3TriggersExist(pParse, pTab, TokenKeyword.TK_DELETE, null, ref iDummy);
            isView = pTab.pSelect != null;
#else
      const Trigger pTrigger = null;
      bool isView = false;
#endif
#if SQLITE_OMIT_VIEW
//# undef isView
isView = false;
#endif

            /* If pTab is really a view, make sure it has been initialized.
*/
            if (Build.ViewGetColumnNames(pParse, pTab) != 0)
            {
                goto delete_from_cleanup;
            }

            if (sqlite3IsReadOnly(pParse, pTab, (pTrigger != null ? 1 : 0)))
            {
                goto delete_from_cleanup;
            }
            iDb = sqlite3SchemaToIndex(db, pTab.pSchema);
            Debug.Assert(iDb < db.nDb);
            zDb = db.aDb[iDb].zName;
#if !SQLITE_OMIT_AUTHORIZATION
rcauth = sqlite3AuthCheck(pParse, ActionCode.SQLITE_DELETE, pTab->zName, 0, zDb);
#else
            rcauth = StatusCode.SQLITE_OK;
#endif
            Debug.Assert(rcauth == StatusCode.SQLITE_OK || rcauth == SQLITE_DENY || rcauth == SQLITE_IGNORE);
            if (rcauth == SQLITE_DENY)
            {
                goto delete_from_cleanup;
            }
            Debug.Assert(!isView || pTrigger != null);

            /* Assign  cursor number to the table and all its indices.
      */
            Debug.Assert(pTabList.nSrc == 1);
            iCur = pTabList.a[0].iCursor = pParse.nTab++;
            for (pIdx = pTab.pIndex; pIdx != null; pIdx = pIdx.pNext)
            {
                pParse.nTab++;
            }

#if !SQLITE_OMIT_AUTHORIZATION
/* Start the view context
*/
if( isView ){
sqlite3AuthContextPush(pParse, sContext, pTab.zName);
}
#endif
            /* Begin generating code.
*/
            v = SelectHelper.GetVdbe(pParse);
            if (v == null)
            {
                goto delete_from_cleanup;
            }
            if (pParse.nested == 0) VdbeAux.VdbeCountChanges(v);
            Build.BeginWriteOperation(pParse, 1, iDb);

            /* If we are trying to delete from a view, realize that view into
      ** a ephemeral table.
      */
#if !(SQLITE_OMIT_VIEW) && !(SQLITE_OMIT_TRIGGER)
            if (isView)
            {
                sqlite3MaterializeView(pParse, pTab, pWhere, iCur);
            }
#endif
            /* Resolve the column names in the WHERE clause.
      */
            sNC = new NameContext(); // memset( &sNC, 0, sizeof( sNC ) );
            sNC.pParse = pParse;
            sNC.pSrcList = pTabList;
            if (sqlite3ResolveExprNames(sNC, ref pWhere) != 0)
            {
                goto delete_from_cleanup;
            }


            /* Initialize the counter of the number of rows deleted, if
** we are counting rows.
*/
            if ((db.flags & Flag.SQLITE_CountRows) != 0)
            {
                memCnt = ++pParse.nMem;
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Integer, 0, memCnt);
            }

#if !SQLITE_OMIT_TRUNCATE_OPTIMIZATION
            /* Special case: A DELETE without a WHERE clause deletes everything.
  ** It is easier just to erase the whole table. Prior to version 3.6.5,
  ** this optimization caused the row change count (the value returned by 
  ** API function sqlite3_count_changes) to be set incorrectly.  */
            if (rcauth == StatusCode.SQLITE_OK && pWhere == null && null == pTrigger && !Utility.IsVirtual(pTab)
                && 0 == sqlite3FkRequired(pParse, pTab, null, 0)
                )
            {
                Debug.Assert(!isView);
                VdbeAux.VdbeAddOp4(v, OPCode.OP_Clear, pTab.tnum, iDb, memCnt,
                                  pTab.zName, P4Type.P4_STATIC);
                for (pIdx = pTab.pIndex; pIdx != null; pIdx = pIdx.pNext)
                {
                    Debug.Assert(pIdx.pSchema == pTab.pSchema);
                    VdbeAux.VdbeAddOp2(v, OPCode.OP_Clear, pIdx.tnum, iDb);
                }
            }
            else
#endif
                //* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */
                /* The usual case: There is a WHERE clause so we have to scan through
** the table and pick which records to delete.
*/
            {
                int iRowSet = ++pParse.nMem; /* Register for rowset of rows to delete */
                int iRowid = ++pParse.nMem; /* Used for storing rowid values. */
                int regRowid; /* Actual register containing rowids */

                /* Collect rowids of every row to be deleted.
        */
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Null, 0, iRowSet);
                ExprList elDummy = null;
                pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, ref elDummy, WhereFlag.WHERE_DUPLICATES_OK);
                if (pWInfo == null) goto delete_from_cleanup;
                regRowid = ExprHelper.ExprCodeGetColumn(pParse, pTab, -1, iCur, iRowid);
                VdbeAux.VdbeAddOp2(v, OPCode.OP_RowSetAdd, iRowSet, regRowid);
                if ((db.flags & Flag.SQLITE_CountRows) != 0)
                {
                    VdbeAux.VdbeAddOp2(v, OPCode.OP_AddImm, memCnt, 1);
                }

                sqlite3WhereEnd(pWInfo);

                /* Delete every item whose key was written to the list during the
        ** database scan.  We have to delete items after the scan is complete
        ** because deleting an item can change the scan order. */
                end = VdbeAux.VdbeMakeLabel(v);

                /* Unless this is a view, open cursors for the table we are 
        ** deleting from and all its indices. If this is a view, then the
        ** only effect this statement has is to fire the INSTEAD OF 
        ** triggers.  */
                if (!isView)
                {
                    sqlite3OpenTableAndIndices(pParse, pTab, iCur, OPCode.OP_OpenWrite);
                }

                addr = VdbeAux.VdbeAddOp3(v, OPCode.OP_RowSetRead, iRowSet, end, iRowid);

                /* Delete the row */
#if !SQLITE_OMIT_VIRTUALTABLE
if( Utility.IsVirtual(pTab) ){
const char *pVTab = (const char *)sqlite3GetVTable(db, pTab);
sqlite3VtabMakeWritable(pParse, pTab);
VdbeAux.VdbeAddOp4(v, OPCode.OP_VUpdate, 0, 1, iRowid, pVTab, P4Type.P4_VTAB);
Build.MayAbort(pParse);
}else
#endif
                {
                    int count = (pParse.nested == 0) ? 1 : 0; /* True to count changes */
                    sqlite3GenerateRowDelete(pParse, pTab, iCur, iRowid, count, pTrigger, OnConstraintError.OE_Default);
                }

                /* End of the delete loop */
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Goto, 0, addr);
                VdbeAux.VdbeResolveLabel(v, end);

                /* Close the cursors open on the table and its indexes. */
                if (!isView && !Utility.IsVirtual(pTab))
                {
                    for (i = 1, pIdx = pTab.pIndex; pIdx != null; i++, pIdx = pIdx.pNext)
                    {
                        VdbeAux.VdbeAddOp2(v, OPCode.OP_Close, iCur + i, pIdx.tnum);
                    }
                    VdbeAux.VdbeAddOp1(v, OPCode.OP_Close, iCur);
                }
            }

            /* Update the sqlite_sequence table by storing the content of the
      ** maximum rowid counter values recorded while inserting into
      ** autoincrement tables.
      */
            if (pParse.nested == 0 && pParse.pTriggerTab == null)
            {
                sqlite3AutoincrementEnd(pParse);
            }

            /* Return the number of rows that were deleted. If this routine is 
      ** generating code because of a call to Build.NestedParse(), do not
      ** invoke the callback function.
      */

            if ((db.flags & Flag.SQLITE_CountRows) != 0 && 0 == pParse.nested && null == pParse.pTriggerTab)
            {
                VdbeAux.VdbeAddOp2(v, OPCode.OP_ResultRow, memCnt, 1);
                VdbeAux.VdbeSetNumCols(v, 1);
                VdbeAux.VdbeSetColName(v, 0, VdbeColnameType.COLNAME_NAME, "rows deleted", Const.SQLITE_STATIC);
            }

            delete_from_cleanup:
#if !SQLITE_OMIT_AUTHORIZATION
sqlite3AuthContextPop(sContext);
#endif
            Build.SrcListDelete(db, ref pTabList);
            ExprHelper.ExprDelete(db, ref pWhere);
            return;
        }

        /* Make sure "isView" and other macros defined above are undefined. Otherwise
    ** thely may interfere with compilation of other functions in this file
    ** (or in another file, if this file becomes part of the amalgamation).  */
        //#ifdef isView
        // #undef isView
        //#endif
        //#ifdef pTrigger
        // #undef pTrigger
        //#endif

        /*
    ** This routine generates VDBE code that causes a single row of a
    ** single table to be deleted.
    **
    ** The VDBE must be in a particular state when this routine is called.
    ** These are the requirements:
    **
    **   1.  A read/write cursor pointing to pTab, the table containing the row
    **       to be deleted, must be opened as cursor number $iCur.
    **
    **   2.  Read/write cursors for all indices of pTab must be open as
    **       cursor number base+i for the i-th index.
    **
    **   3.  The record number of the row to be deleted must be stored in
    **       memory cell iRowid.
    **
    ** This routine generates code to remove both the table record and all 
    ** index entries that point to that record.
    */

        private static void sqlite3GenerateRowDelete(
            Parse pParse, /* Parsing context */
            Table pTab, /* Table containing the row to be deleted */
            int iCur, /* VdbeCursor number for the table */
            int iRowid, /* Memory cell that contains the rowid to delete */
            int count, /* If non-zero, increment the row change counter */
            Trigger pTrigger, /* List of triggers to (potentially) fire */
            int onconf /* Default ON CONFLICT policy for triggers */
            )
        {
            Vdbe v = pParse.pVdbe; /* Vdbe */
            int iOld = 0; /* First register in OLD.* array */
            int iLabel; /* Label resolved to end of generated code */

            /* Vdbe is guaranteed to have been allocated by this stage. */
            Debug.Assert(v != null);

            /* Seek cursor iCur to the row to delete. If this row no longer exists 
      ** (this can happen if a trigger program has already deleted it), do
      ** not attempt to delete it or fire any DELETE triggers.  */
            iLabel = VdbeAux.VdbeMakeLabel(v);
            VdbeAux.VdbeAddOp3(v, OPCode.OP_NotExists, iCur, iLabel, iRowid);

            /* If there are any triggers to fire, allocate a range of registers to
      ** use for the old.* references in the triggers.  */
            if (sqlite3FkRequired(pParse, pTab, null, 0) != 0 || pTrigger != null)
            {
                uint mask; /* Mask of OLD.* columns in use */
                int iCol; /* Iterator used while populating OLD.* */

                /* TODO: Could use temporary registers here. Also could attempt to
        ** avoid copying the contents of the rowid register.  */
                mask = sqlite3TriggerColmask(
                    pParse, pTrigger, null, 0, TRIGGER_BEFORE | TRIGGER_AFTER, pTab, onconf
                    );
                mask |= sqlite3FkOldmask(pParse, pTab);
                iOld = pParse.nMem + 1;
                pParse.nMem += (1 + pTab.nCol);

                /* Populate the OLD.* pseudo-table register array. These values will be 
        ** used by any BEFORE and AFTER triggers that exist.  */
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Copy, iRowid, iOld);
                for (iCol = 0; iCol < pTab.nCol; iCol++)
                {
                    if (mask == 0xffffffff || (mask & (1 << iCol)) != 0)
                    {
                        int iTarget = iOld + iCol + 1;
                        VdbeAux.VdbeAddOp3(v, OPCode.OP_Column, iCur, iCol, iTarget);
                        sqlite3ColumnDefault(v, pTab, iCol, iTarget);
                    }
                }

                /* Invoke BEFORE DELETE trigger programs. */
                sqlite3CodeRowTrigger(pParse, pTrigger,
                                      TokenKeyword.TK_DELETE, null, TRIGGER_BEFORE, pTab, iOld, onconf, iLabel
                    );

                /* Seek the cursor to the row to be deleted again. It may be that
        ** the BEFORE triggers coded above have already removed the row
        ** being deleted. Do not attempt to delete the row a second time, and 
        ** do not fire AFTER triggers.  */
                VdbeAux.VdbeAddOp3(v, OPCode.OP_NotExists, iCur, iLabel, iRowid);

                /* Do FK processing. This call checks that any FK constraints that
        ** refer to this table (i.e. constraints attached to other tables) 
        ** are not violated by deleting this row.  */
                sqlite3FkCheck(pParse, pTab, iOld, 0);
            }

            /* Delete the index and table entries. Skip this step if pTab is really
      ** a view (in which case the only effect of the DELETE statement is to
      ** fire the INSTEAD OF triggers).  */
            if (pTab.pSelect == null)
            {
                sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, 0);
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Delete, iCur, (count != 0 ? P5Value.OPFLAG_NCHANGE : 0));
                if (count != 0)
                {
                    VdbeAux.VdbeChangeP4(v, -1, pTab.zName, P4Type.P4_STATIC);
                }
            }

            /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to
      ** handle rows (possibly in other tables) that refer via a foreign key
      ** to the row just deleted. */
            sqlite3FkActions(pParse, pTab, null, iOld);

            /* Invoke AFTER DELETE trigger programs. */
            sqlite3CodeRowTrigger(pParse, pTrigger,
                                  TokenKeyword.TK_DELETE, null, TRIGGER_AFTER, pTab, iOld, onconf, iLabel
                );

            /* Jump here if the row had already been deleted before any BEFORE
      ** trigger programs were invoked. Or if a trigger program throws a 
      ** RAISE(IGNORE) exception.  */
            VdbeAux.VdbeResolveLabel(v, iLabel);
        }


        /*
    ** This routine generates VDBE code that causes the deletion of all
    ** index entries associated with a single row of a single table.
    **
    ** The VDBE must be in a particular state when this routine is called.
    ** These are the requirements:
    **
    **   1.  A read/write cursor pointing to pTab, the table containing the row
    **       to be deleted, must be opened as cursor number "iCur".
    **
    **   2.  Read/write cursors for all indices of pTab must be open as
    **       cursor number iCur+i for the i-th index.
    **
    **   3.  The "iCur" cursor must be pointing to the row that is to be
    **       deleted.
    */

        private static void sqlite3GenerateRowIndexDelete(
            Parse pParse, /* Parsing and code generating context */
            Table pTab, /* Table containing the row to be deleted */
            int iCur, /* VdbeCursor number for the table */
            int nothing /* Only delete if aRegIdx!=0 && aRegIdx[i]>0 */
            )
        {
            int[] aRegIdx = null;
            sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, aRegIdx);
        }

        private static void sqlite3GenerateRowIndexDelete(
            Parse pParse, /* Parsing and code generating context */
            Table pTab, /* Table containing the row to be deleted */
            int iCur, /* VdbeCursor number for the table */
            int[] aRegIdx /* Only delete if aRegIdx!=0 && aRegIdx[i]>0 */
            )
        {
            int i;
            Index pIdx;
            int r1;

            for (i = 1, pIdx = pTab.pIndex; pIdx != null; i++, pIdx = pIdx.pNext)
            {
                if (aRegIdx != null && aRegIdx[i - 1] == 0) continue;
                r1 = sqlite3GenerateIndexKey(pParse, pIdx, iCur, 0, false);
                VdbeAux.VdbeAddOp3(pParse.pVdbe, OPCode.OP_IdxDelete, iCur + i, r1, pIdx.nColumn + 1);
            }
        }

        /*
    ** Generate code that will assemble an index key and put it in register
    ** regOut.  The key with be for index pIdx which is an index on pTab.
    ** iCur is the index of a cursor open on the pTab table and pointing to
    ** the entry that needs indexing.
    **
    ** Return a register number which is the first in a block of
    ** registers that holds the elements of the index key.  The
    ** block of registers has already been deallocated by the time
    ** this routine returns.
    */

        private static int sqlite3GenerateIndexKey(
            Parse pParse, /* Parsing context */
            Index pIdx, /* The index for which to generate a key */
            int iCur, /* VdbeCursor number for the pIdx.pTable table */
            int regOut, /* Write the new index key to this register */
            bool doMakeRec /* Run the OPCode.OP_MakeRecord instruction if true */
            )
        {
            Vdbe v = pParse.pVdbe;
            int j;
            Table pTab = pIdx.pTable;
            int regBase;
            int nCol;

            nCol = pIdx.nColumn;
            regBase = ExprHelper.GetTempRange(pParse, nCol + 1);
            VdbeAux.VdbeAddOp2(v, OPCode.OP_Rowid, iCur, regBase + nCol);
            for (j = 0; j < nCol; j++)
            {
                int idx = pIdx.aiColumn[j];
                if (idx == pTab.iPKey)
                {
                    VdbeAux.VdbeAddOp2(v, OPCode.OP_SCopy, regBase + nCol, regBase + j);
                }
                else
                {
                    VdbeAux.VdbeAddOp3(v, OPCode.OP_Column, iCur, idx, regBase + j);
                    sqlite3ColumnDefault(v, pTab, idx, -1);
                }
            }
            if (doMakeRec)
            {
                VdbeAux.VdbeAddOp3(v, OPCode.OP_MakeRecord, regBase, nCol + 1, regOut);
                VdbeAux.VdbeChangeP4(v, -1, sqlite3IndexAffinityStr(v, pIdx), 0);
            }
            ExprHelper.ReleaseTempRange(pParse, regBase, nCol + 1);
            return regBase;
        }
    }
}